﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace SourcePacker
{
    class SourceFile
    {
        public SourceFile(string fullPath)
        {
            var info = new FileInfo(fullPath);
            
            FullPath = fullPath;
            Path = info.DirectoryName;
            Extension = info.Extension;
            FileName = info.Name;
            Length = info.Length;
            Text = File.ReadAllText(fullPath);
            Lines = File.ReadAllLines(fullPath);
            
            Includes = new List<Include>();
            References = new List<SourceFile>();
        }
        
        public string FullPath { get; private set; }
        public string Path { get; private set; }
        public string Extension { get; private set; }
        public string Text { get; private set; }
        public long Length { get; private set; }
        public string[] Lines { get; private set; }
        public string FileName { get; private set; }
        public List<Include> Includes { get; private set; }
        public List<SourceFile> References { get; private set; }

        // --- params ---
        string[] additionalIncludeFolders;
        bool systemInclude;
        Dictionary<string, SourceFile>[] knownSourceFiles;
        Dictionary<string, SourceFile> includedFiles;

        /// <summary>
        /// 分析所有的包含文件
        /// </summary>
        /// <param name="additionalIncludeFolders">附加的包含目录</param>
        /// <param name="systemInclude">是否包括系统包含目录</param>
        /// <param name="knownSourceFiles">已知的源文件列表</param>
        /// <param name="includedFiles">包含文件列表</param>
        public void analyseIncludes(
            string[] additionalIncludeFolders,
            bool systemInclude, 
            Dictionary<string, SourceFile>[] knownSourceFiles, 
            ref Dictionary<string, SourceFile> includedFiles)
        {
            this.additionalIncludeFolders = additionalIncludeFolders;
            this.systemInclude = systemInclude;
            this.knownSourceFiles = knownSourceFiles;
            this.includedFiles = includedFiles;
            Includes.Clear();

            string includedFileName;
            SourceFile includedFile;
            IncludeLocator locator = new IncludeLocator(this);

            foreach (var result in locator.Results)
            {
                includedFileName = Text.Substring(result.Value.Key, result.Value.Value - result.Value.Key);
                includedFileName.Replace('/', '\\');

                if (tryLocateIncludedFile(includedFileName, out includedFile))
                {
                    Includes.Add(new Include(includedFile, result.Key, result.Value.Value - result.Key));
                    includedFile.References.Add(this);
                }
            }

            //Regex regex = new Regex(@"[\s]*?#[\s]*?include");
            //int beginLine, endLine;
            //string includedFileName;
            //SourceFile includedFile;

            //this.additionalIncludeFolders = additionalIncludeFolders;
            //this.systemInclude = systemInclude;
            //this.knownSourceFiles = knownSourceFiles;
            //this.includedFiles = includedFiles;
            //Includes.Clear();

            //for (int i = 0; i < Lines.Length; ++i)
            //{
            //    if (regex.Match(Lines[i]).Success)
            //    {
            //        beginLine = endLine = i;

            //        if (!Lines[i].EndsWith("\\"))
            //            includedFileName = Lines[i].Substring(regex.Match(Lines[i]).Length);
            //        else
            //        {
            //            while (Lines[i].EndsWith("\\") && ++i < Lines.Length) { ++endLine; }
            //            if (i >= Lines.Length) break;
            //            includedFileName = Lines[i];
            //        }

            //        if (tryLocateIncludedFile(includedFileName,out includedFile))
            //        {
            //            Includes.Add(new Include(includedFile, beginLine, endLine + 1 - beginLine));
            //            includedFile.References.Add(this);
            //        }
            //    }
            //}
        }



        /// <summary>
        /// 尝试定位包含文件
        /// </summary>
        /// <param name="includedFileName">包含文件名称（含双引号或尖括号）</param>
        /// <param name="sourceFile">定位出的源文件对象</param>
        /// <returns>定位成功返回true，否则返回false</returns>
        bool tryLocateIncludedFile(string includedFileName,out SourceFile sourceFile)
        {
            sourceFile = null;
            includedFileName = includedFileName.Trim();
            if (includedFileName.Length <= 2) return false;

            char firstChar = includedFileName[0];
            includedFileName = includedFileName.Substring(1, includedFileName.Length - 2);

            if (firstChar == '\"')
                return tryLocateIncludedFileInLocalPath(includedFileName, out sourceFile) ||
                    tryLocateIncludedFileInAdditionalPath(includedFileName,  out sourceFile);
            else if (systemInclude && firstChar == '<')
                return tryLocateIncludedFileInAdditionalPath(includedFileName, out sourceFile) ||
                    tryLocateIncludedFileInLocalPath(includedFileName, out sourceFile);

            return false;
        }

        /// <summary>
        /// 尝试在相对本文件的位置查找包含文件（绝对路径也可定位成功）
        /// </summary>
        /// <param name="includedFileName">包含文件名称（不含双引号或尖括号）</param>
        /// <param name="sourceFile">定位出的源文件对象</param>
        /// <returns>定位成功返回true，否则返回false</returns>
        bool tryLocateIncludedFileInLocalPath(string includedFileName,out SourceFile sourceFile)
        {
            return tryLocateIncludedFileInFolder(includedFileName, Path, out sourceFile);
        }

        /// <summary>
        /// 尝试在附加包含目录中查找包含文件（绝对路径也可定位成功）
        /// </summary>
        /// <param name="includedFileName">包含文件名称（含双引号或尖括号）</param>
        /// <param name="sourceFile">定位出的源文件对象</param>
        /// <returns>定位成功返回true，否则返回false</returns>
        bool tryLocateIncludedFileInAdditionalPath(
            string includeFileName, 
            out SourceFile sourceFile)
        {
            foreach(var path in additionalIncludeFolders)
                if (tryLocateIncludedFileInFolder(includeFileName, path, out sourceFile))
                    return true;

            sourceFile = null;
            return false;
        }

        /// <summary>
        /// 尝试在指定单个目录中查找包含文件（绝对路径也可定位成功）
        /// </summary>
        /// <param name="includedFileName">包含文件名称（含双引号或尖括号）</param>
        /// <param name="folder">指定目录</param>
        /// <param name="sourceFile">定位出的源文件对象</param>
        /// <returns>定位成功返回true，否则返回false</returns>
        bool tryLocateIncludedFileInFolder(
            string includedFileName, 
            string folder, 
            out SourceFile sourceFile)
        {
            string fullPath = System.IO.Path.Combine(folder, includedFileName);

            if (System.IO.File.Exists(fullPath))
            {
                sourceFile = getSourceFile(fullPath);
                return true;
            }

            sourceFile = null;
            return false;
        }

        /// <summary>
        /// 获取文件全路径对应的SourceFile对象
        /// </summary>
        /// <param name="fullPath">文件全路径</param>
        /// <returns>返回对应的SourceFile对象</returns>
        /// <remarks>如果已知源文件列表或者包含文件列表中已存在此文件，则直接返回对应的SourceFile对象，否则新建，并添加到包含文件列表中</remarks>
        SourceFile getSourceFile(string fullPath)
        {
            SourceFile sourceFile;

            foreach (var sources in knownSourceFiles)
                if (sources.TryGetValue(fullPath, out sourceFile)) return sourceFile;
            if (includedFiles.TryGetValue(fullPath, out sourceFile)) return sourceFile;

            sourceFile = new SourceFile(fullPath);
            includedFiles.Add(fullPath, sourceFile);
            return sourceFile;
        }
    }
}
